package com.chasing.baseframe.util;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import androidx.fragment.app.FragmentActivity;

import com.chasing.baseframe.MyApplication;
import com.chasing.baseframe.R;
import com.chasing.baseframe.common.CommonFunctionManager;
import com.chasing.baseframe.db.entity.FileDownloadCacheEntity;
import com.chasing.baseframe.db.manager.FileDownloadCacheManager;

import net.chasing.androidbaseconfig.util.CollectionUtils;
import net.chasing.androidbaseconfig.util.FileUtils;
import net.chasing.androidbaseconfig.util.permission.PermissionUtils;
import net.chasing.androidbaseconfig.util.ToastUtils;
import net.chasing.androidbaseconfig.util.thread.ComparableRunnable;
import net.chasing.androidbaseconfig.util.thread.ThreadPoolUtils;
import net.chasing.retrofit.RequestManager;
import net.chasing.retrofit.bean.req.DownloadInfoReq;
import net.chasing.retrofit.callback.ResponseBodyCallback;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import io.reactivex.rxjava3.subscribers.ResourceSubscriber;
import okhttp3.ResponseBody;

/**
 * 在每个界面退出时，需要调用disposeAll()避免还在下载而持有context造成内存泄露
 */
public class FileDownloadUtils {
    public static final byte TYPE_PICTURES = 0;
    public static final byte TYPE_VIDEO = 1;
    public static final byte TYPE_DOCUMENT = 2;
    public static final byte TYPE_AUDIO = 3;

    public static boolean isShowSavePath = true; //是否弹toast展示存储路径
    private static final Map<String, ResourceSubscriber<ResponseBody>> mSubscriberMap = new HashMap<>();
    private static final Handler mHandler = new Handler(Looper.getMainLooper());
    // 适配Android10：需要保存的地址和实际存储的地址会不同
    private static final HashMap<String, String> mFilePathLinkAndroid10 = new HashMap<>();

    private FileDownloadUtils() {
    }

    //停止下载
    public static void dispose(String url) {
        if (mSubscriberMap.containsKey(url)) {
            ResourceSubscriber<ResponseBody> responseBodyResourceSubscriber = mSubscriberMap.get(url);
            if (responseBodyResourceSubscriber != null && !responseBodyResourceSubscriber.isDisposed())
                responseBodyResourceSubscriber.dispose();
            mSubscriberMap.remove(url);
        }
    }

    public static void disposeAll() {
        if (!mSubscriberMap.isEmpty()) {
            Collection<ResourceSubscriber<ResponseBody>> values = mSubscriberMap.values();
            for (ResourceSubscriber<ResponseBody> value : values) {
                if (value != null && !value.isDisposed())
                    value.dispose();
            }
            mSubscriberMap.clear();
        }
        mFilePathLinkAndroid10.clear();
    }

    public static boolean isDownloading(String url) {
        return mSubscriberMap.containsKey(url);
    }

    //普通的下载，不进行断点下载，适用小文件下载，图片下载
    @SuppressLint("DefaultLocale")
    public static void download(Context context, byte downloadType, String url, OnDownloadFinishListener listener) {
        if (FileUtils.isFilePath(url)) {
            ToastUtils.showShort(MyApplication.INSTANCE, "本地文件无需下载");
            return;
        }
        if (PermissionUtils.requestStoragePermission((FragmentActivity) context, isAllGranted -> {
            if (isAllGranted)
                download(context, downloadType, url, listener);
        })) return;

        String filePath;
        if (url.contains("?"))
            filePath = url.substring(0, url.lastIndexOf('?'));//去除参数  -->  http://www.fffff.jlkj/djja.jpg?size=220*22;
        else
            filePath = url;
        filePath = filePath.replace("\\", "/");
        filePath = FileUtils.getDownloadPath() + filePath.substring(filePath.lastIndexOf("/") + 1);
        if (!filePath.contains(".")) { //未知格式，统一按图片处理
            filePath += ".jpg";
        }
        filePath = FileUtils.judgeFilePathExist(MyApplication.INSTANCE, filePath);
        download(context, downloadType, url, filePath, listener);
    }

    public static void download(Context context, byte downloadType, String url, String filePath, OnDownloadFinishListener listener) {
        if (TextUtils.isEmpty(url) || TextUtils.isEmpty(filePath)) {
            ToastUtils.showShort(context, "链接异常");
            return;
        } else if (mSubscriberMap.get(url) != null) //正在下载了
            return;
        if (PermissionUtils.requestStoragePermission((FragmentActivity) context, isAllGranted -> {
            if (isAllGranted)
                download(context, downloadType, url, filePath, listener);
        })) return;
        ResourceSubscriber<ResponseBody> subscriber = RequestManager.getInstance().downloadFile(url, new ResponseBodyCallback() {
            @Override
            public void onSuccess(final ResponseBody response) {
                ThreadPoolUtils.getInstance().execute(new ComparableRunnable() {
                    @Override
                    public void run() {
                        DownloadInfoReq downloadInfoReq = new DownloadInfoReq();
                        downloadInfoReq.setSavePath(filePath);
                        downloadInfoReq.setUrl(url);
                        if (FileUtils.isBelow29() || FileUtils.isSelfFile(MyApplication.INSTANCE, filePath))
                            writeCache(context, response, downloadInfoReq, listener, null);
                        else
                            writeCacheAndroid10(context, downloadType, response, downloadInfoReq, listener, null);
                    }
                });
            }

            @Override
            public void onFailure(String msg) {
                mSubscriberMap.remove(url);
                if (msg.contains("404"))
                    ToastUtils.showShort(context, "不存在该文件！");
                else
                    CommonFunctionManager.requestFailedTip(msg);
                if (listener != null)
                    listener.downloadFail(msg.contains("404"));
            }
        });
        if (subscriber != null)
            mSubscriberMap.put(url, subscriber);
    }

    //下载大文件，可以进行断点继续下载，前提：需要传入相同的url
    //会将下载信息存进FileDownloadDao表
    //目前该方法仅用于下载文档
    public static void downloadLargeFile(Context context, String url, String filePath, OnDownloadFinishListener listener) {
        if (PermissionUtils.requestStoragePermission((FragmentActivity) context, isAllGranted -> {
            if (isAllGranted)
                downloadLargeFile(context, url, filePath, listener);
        })) return;

        if (mSubscriberMap.containsKey(url)) {
            ResourceSubscriber<ResponseBody> responseBodyResourceSubscriber = mSubscriberMap.get(url);
            if (responseBodyResourceSubscriber != null && !responseBodyResourceSubscriber.isDisposed())
                responseBodyResourceSubscriber.dispose();
            mSubscriberMap.remove(url);
        }

        FileDownloadCacheManager.getInstance().getByUrl(url, fileDownloadCacheEntities -> {
            if (CollectionUtils.isEmpty(fileDownloadCacheEntities))
                downloadLargeFile(context, url, filePath, listener, null);
            else
                downloadLargeFile(context, url, filePath, listener, fileDownloadCacheEntities.get(0));
        });
    }

    private static void downloadLargeFile(Context context, String url, String filePath, OnDownloadFinishListener listener,
                                          FileDownloadCacheEntity cache) {
        final DownloadInfoReq info = new DownloadInfoReq();
        info.setSavePath(filePath);
        info.setUrl(url);
        info.setReadLength(cache == null ? 0 : FileUtils.getFileLength(cache.getFilePath()));
        info.setTotalLength(cache == null ? 0 : cache.getFileTotalLength());
        ResourceSubscriber<ResponseBody> subscriber = RequestManager.getInstance().downloadFile(info, new ResponseBodyCallback() {
            @Override
            public void onSuccess(final ResponseBody response) {
                super.onSuccess(response);
                ThreadPoolUtils.getInstance().execute(new ComparableRunnable() {
                    @Override
                    public void run() {
                        if (FileUtils.isBelow29() || FileUtils.isSelfFile(context, filePath))
                            writeCache(context, response, info, listener, cache);
                        else
                            writeCacheAndroid10(context, TYPE_DOCUMENT, response, info, listener, cache);
                    }
                });
            }

            @Override
            public void onFailure(String msg) {
                super.onFailure(msg);
                mSubscriberMap.remove(url);
                if (msg.contains("404"))
                    ToastUtils.showShort(context, "不存在该文件！");
                else
                    CommonFunctionManager.requestFailedTip(msg);
                if (listener != null)
                    listener.downloadFail(msg.contains("404"));
            }
        });
        if (subscriber != null)
            mSubscriberMap.put(url, subscriber);
    }

    /**
     * 写入文件
     *
     * @param info 下载的信息（包含文件总长度、存储位置等）
     */
    private static void writeCache(final Context context, ResponseBody responseBody, final DownloadInfoReq info,
                                   final OnDownloadFinishListener listener, FileDownloadCacheEntity cache) {
        if (responseBody == null) {
            ToastUtils.showShort(context.getApplicationContext(), "数据源错误");
            return;
        }

        if (cache == null)
            cache = new FileDownloadCacheEntity();
        cache.setFilePath(info.getSavePath());
        cache.setUrl(info.getUrl());

        File file = new File(info.getSavePath());
        if (file.getParentFile() != null && !file.getParentFile().exists())
            file.getParentFile().mkdirs();
        RandomAccessFile randomAccessFile = null;
        InputStream inputStream = null;
        try {
            inputStream = responseBody.byteStream();
            long totalLength = responseBody.contentLength();
            if (info.getTotalLength() != 0) {
                totalLength = info.getTotalLength();
            }
            cache.setFileTotalLength(totalLength);
            randomAccessFile = new RandomAccessFile(file, "rw");
            randomAccessFile.seek(info.getReadLength());
            byte[] buffer = new byte[1024 * 4];
            int len;
            long totalLen = info.getReadLength();
            FileDownloadCacheManager.getInstance().save(cache);
            while ((len = inputStream.read(buffer)) != -1) {
                randomAccessFile.write(buffer, 0, len);
                info.setReadLength(info.getReadLength() + len);
                totalLen += len;
                if (listener != null) {
                    final long finalTotalLen = totalLen;
                    final long finalTotalLength = totalLength;
                    mHandler.post(() -> listener.downloadProgress((int) ((finalTotalLen / (float) finalTotalLength) * 100)));
                }
            }
            mSubscriberMap.remove(info.getUrl());
            cache.setSuccess(true);
            FileDownloadCacheManager.getInstance().save(cache);
            if (listener != null) {
                mHandler.post(() -> {
                    listener.downloadProgress(100);
                    listener.downloadSuccess(info.getSavePath());
                    if (isShowSavePath) {
                        String savePath = info.getSavePath();
                        if (savePath != null)
                            savePath = savePath.replace("/storage/emulated/0", "内部存储");
                        ToastUtils.showShort(context, context.getString(R.string.save_file_success, savePath));
                    }
                });
            }
            FileUtils.updateGallery(context, info.getSavePath());
        } catch (Exception e) {
            mSubscriberMap.remove(info.getUrl());
            if (e.getMessage() != null && e.getMessage().contains("No space left on device")) {
                ToastUtils.showShort(MyApplication.INSTANCE, "内存不足，请清理后重试");
            } else {
                ToastUtils.showShort(MyApplication.INSTANCE, "下载中断");
            }
            if (e.getMessage() != null && e.getMessage().toLowerCase().contains("file")) {
                FileDownloadCacheManager.getInstance().delete(cache); // 文件出现问题，进行删除，避免一直不能下载
            }
            if (listener != null)
                mHandler.post(() -> listener.downloadFail(false));
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
                if (randomAccessFile != null)
                    randomAccessFile.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    private static void writeCacheAndroid10(Context context, byte downloadType, ResponseBody responseBody,
                                            DownloadInfoReq info, OnDownloadFinishListener listener,
                                            FileDownloadCacheEntity cache) {
        if (responseBody == null) {
            ToastUtils.showShort(context.getApplicationContext(), "数据源错误");
            return;
        }

        String fileName = info.getSavePath().replace("\\", "/");
        fileName = fileName.substring(fileName.lastIndexOf("/") + 1);

        byte[] preData = null;
        if (!TextUtils.isEmpty(mFilePathLinkAndroid10.get(info.getSavePath()))) {
            preData = FileUtils.getFileByteArray(context, mFilePathLinkAndroid10.get(info.getSavePath()));
            FileUtils.deleteDownloadDocument(context, fileName);
        }

        Uri uri = null;
        if (downloadType == TYPE_PICTURES) {
            uri = FileUtils.getDownloadPicUri(context, fileName);
        } else if (downloadType == TYPE_VIDEO) {
            uri = FileUtils.getDownloadVideoUri(context, fileName);
        } else if (downloadType == TYPE_DOCUMENT) {
            uri = FileUtils.getDownloadDocumentUri(context, fileName);
        } else if (downloadType == TYPE_AUDIO) {
            uri = FileUtils.getDownloadAudioUri(context, fileName);
        }
        if (uri == null) {
            ToastUtils.showShort(context.getApplicationContext(), "文件生成失败");
            mSubscriberMap.remove(info.getUrl());
            if (listener != null)
                mHandler.post(() -> listener.downloadFail(false));
            return;
        }

        OutputStream os = null;
        try {
            os = context.getContentResolver().openOutputStream(uri);
        } catch (FileNotFoundException ignored) {
        }
        if (os == null) {
            ToastUtils.showShort(context.getApplicationContext(), "文件生成失败");
            mSubscriberMap.remove(info.getUrl());
            if (listener != null)
                mHandler.post(() -> listener.downloadFail(false));
            return;
        }

        String savePath = FileUtils.getFilePathByUri(context, uri);
        mFilePathLinkAndroid10.put(info.getSavePath(), savePath);

        if (cache == null)
            cache = new FileDownloadCacheEntity();
        cache.setFilePath(info.getSavePath());
        cache.setUrl(info.getUrl());

        InputStream inputStream = null;
        try {
            if (preData != null)
                os.write(preData);
            inputStream = responseBody.byteStream();
            long totalLength = responseBody.contentLength();
            if (info.getTotalLength() != 0) {
                totalLength = info.getTotalLength();
            }
            cache.setFileTotalLength(totalLength);
            byte[] buffer = new byte[1024 * 4];
            int len;
            long readLength = info.getReadLength();
            FileDownloadCacheManager.getInstance().save(cache);
            while ((len = inputStream.read(buffer)) != -1) {
                os.write(buffer, 0, len);
                info.setReadLength(info.getReadLength() + len);
                readLength += len;
                if (listener != null) {
                    final long readLength1 = readLength;
                    final long finalTotalLength = totalLength;
                    mHandler.post(() -> listener.downloadProgress((int) ((readLength1 / (float) finalTotalLength) * 100)));
                }
            }
            os.flush();
            mSubscriberMap.remove(info.getUrl());
            mFilePathLinkAndroid10.remove(info.getSavePath());
            cache.setSuccess(true);
            FileDownloadCacheManager.getInstance().save(cache);
            if (listener != null) {
                mHandler.post(() -> {
                    listener.downloadProgress(100);
                    listener.downloadSuccess(savePath);
                    if (isShowSavePath) {
                        String infoSavePath = savePath;
                        if (infoSavePath != null)
                            infoSavePath = infoSavePath.replace("/storage/emulated/0", "内部存储");
                        ToastUtils.showShort(context, context.getString(R.string.save_file_success, infoSavePath));
                    }
                });
            }
        } catch (Exception e) {
            mSubscriberMap.remove(info.getUrl());
            if (e.getMessage() != null && e.getMessage().contains("No space left on device")) {
                ToastUtils.showShort(MyApplication.INSTANCE, "内存不足，请清理后重试");
            } else {
                ToastUtils.showShort(MyApplication.INSTANCE, "下载中断");
            }
            if (e.getMessage() != null && e.getMessage().toLowerCase().contains("file")) {
                FileDownloadCacheManager.getInstance().delete(cache); // 文件出现问题，进行删除，避免一直不能下载
            }
            if (listener != null)
                mHandler.post(() -> listener.downloadFail(false));
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
                os.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    public interface OnDownloadFinishListener {
        void downloadSuccess(String filePath);

        void downloadProgress(int progress);

        void downloadFail(boolean isNotFound);
    }

    public abstract static class AbstractDownloadFinishListener implements OnDownloadFinishListener {
        @Override
        public void downloadSuccess(String filePath) {

        }

        @Override
        public void downloadProgress(int progress) {

        }

        @Override
        public void downloadFail(boolean isNotFound) {

        }
    }
}
